import numpy as np
import time

import logging, logging.config, os.path
scriptpath = os.path.dirname(os.path.realpath(__file__))
logging.config.fileConfig(scriptpath+'/logging.conf')
logger = logging.getLogger('camera')


def makeEventDict( run, dictname='eventData'):
    logger.debug("makeEventDict starting up")
    
    pd = run['photonData']
    event_headers=run['corsikaEventHeaders']
    event_ids = pd['event_id']
    max_event_id = int(event_ids.max())

    run[dictname] = {}
    eventDataDict = run[dictname]
    
    for event_id in range(max_event_id):
        eventDataDict[event_id] = {}
        
        #print "event_id",event_id
        ids = np.where(pd['event_id'] == event_id)[0]
        #if not len(ids):
        #    continue
        chids = pd['chid'][ids].astype(int)
        times = pd['time'][ids]-pd['time_to_mirror'][ids]+pd['time_to_focal_plane'][ids]
        if len(times):
            times -= times.min()
        ## The times are in ns, so at this point we convert them to sec.
        times *= 1e-9
        
        eventDataDict[event_id]['chids'] = chids
        eventDataDict[event_id]['times'] = times
    
        timeLists = {}
        for chid,t in zip(chids,times):
            if chid not in timeLists:
                timeLists[chid] = [t]
            else:
                timeLists[chid].append(t)
        
        eventDataDict[event_id]['CherenkovHits'] = timeLists
    
    logger.debug("makeEventDict ended")
    return run


def addNsbAndDarkNoise( run, 
        noiseDictname='PhotonNoiseInfo', 
        outDictname='eventData'):
    
    logger.debug("addNsbAndDarkNoise starting up.")
    
    eventDataDict = run[outDictname]
    
    NsbRate = run['settings'][noiseDictname]['NsbRate']     # stored in Hz
    DcRate = run['settings'][noiseDictname]['DcRate']       # stored in Hz
    Rate = (NsbRate + DcRate)       
    Period = 1./Rate                # in sec
    
    SimulatedTime = run['settings']['SimInfo']['SimulatedTime'] # in sec
    
    for event_id in eventDataDict:
        #print "addNsbAndDarkNoise event_id",event_id
    
        # The rest of the photons, I have in hand are actually falling into this pixel
        NoiseHits = {}
            
        for chid in range(1440):
            #print "chid:",chid, "event_id:",i
            # Generate Background Photons following an exponential with the 
            # expectation value *Period*

            # Expectation value of Photons in the simulated Time * 5 (contingency)
            numberOfNoisePhotons = int(SimulatedTime * Rate)
            timesOfNoisePhotons = np.random.exponential(Period, size=5*numberOfNoisePhotons)
            
            # Since there has to be some time simulated before the start of the Cherenkov Event,
            # I simply assume 1 Period of SimulatedTime before the arrival of the first Photon.
            timesOfNoisePhotons = timesOfNoisePhotons.cumsum() - SimulatedTime
            # timesOfNoisePhotons are now times in ns.
            
            # Now I cut away all Noise Photons which arrived after SimulatedTime
            timesOfNoisePhotons =timesOfNoisePhotons[timesOfNoisePhotons <= SimulatedTime]
            
            for t in timesOfNoisePhotons:
                if chid not in NoiseHits:
                    NoiseHits[chid] = [t]
                else:
                    NoiseHits[chid].append(t)
        
        eventDataDict[event_id]['NoiseHits'] = NoiseHits
    
    logger.debug("addNsbAndDarkNoise ended.")
    return run
    
def simulateOpticalCrosstalk( thedict, 
        eventdictname='eventData',
        hitdictnames = ['NoiseHits', 'CherenkovHits'],
        outdictname = 'AllHitsWithWeights'):
            
    logger.debug("simulateOpticalCrosstalk starting up.")
        
    eventDataDict = thedict[eventdictname]
    
    hdn = hitdictnames
    
    xtalk = thedict['settings']['SipmInfo']['crosstalk_probability']
    weight_generator = thedict['settings']['SipmInfo']['AmplitudeGenerationFunction']
    
    for event_id in eventDataDict:
        
        eventDataDict[event_id][outdictname] = {}
        
        for chid in range(1440):
            
            # gather both times, the times of the CherenkovPhotons AND of the NoisePhotons
            times = []
            for hitdictname in hitdictnames:
                if chid in eventDataDict[event_id][hitdictname]:
                    times += eventDataDict[event_id][hitdictname][chid]
        
            timesNweights = []
            for t in times:
                # Scale the Pulse ... sometimes with 1, sometimes with 2 .. even sometimes with 3 ...
                # and so on... This is my way of adding optical crosstalk
                timesNweights.append( (t, weight_generator(thedict) ) )
        
            eventDataDict[event_id][outdictname][chid] = timesNweights

    logger.debug("simulateOpticalCrosstalk ended.")
    return thedict

def applyTrigger( thedict, 
        coincidenceWindow = 15*1e-9, 
        level=18, 
        keyname='trigger'):
    logger.debug("applyTrigger starting up.")
            
    eventData = thedict['eventData']
    
    triggered_events = 0
    
    for event_id in eventData:
        hits_weights_per_chid = eventData[event_id]['AllHitsWithWeights']
        eventData[event_id][keyname] = False
        
        for patch in range(160):
            hits_weights = None
            for pixel in range(9):
                if hits_weights is None:
                    hits_weights = np.array(hits_weights_per_chid[patch*9+pixel])
                else:
                    hits_weights = np.concatenate( (hits_weights, np.array(hits_weights_per_chid[patch*9+pixel])) )
            
            hits_weights = hits_weights.transpose()
            #print "hits_weights.shape", hits_weights.shape
            
            arrivals = hits_weights.copy()
            departures = hits_weights.copy()
            departures[0] += coincidenceWindow
            departures[1] *= -1.
            
            all = np.concatenate( (arrivals,departures), axis=1)
            
            times = all[0]
            weights = all[1]
            sorter = np.argsort(times)
            weights = weights[sorter]
            times = times[sorter]
            
            photons = weights.cumsum()
            
            if (photons > level).any():
                eventData[event_id][keyname] = True
                triggered_events += 1
                break
                
    
    logger.debug("applyTrigger ended. triggers %d" % (triggered_events))
    return thedict


def applyMostSimpleTrigger( run, level=4 ):
    
    eventDataDict = run['eventData']
    for event_id in eventDataDict:
     
        maxNumPhotonsInPixel =0
        #print "event_id",event_id
        hits = eventDataDict[event_id]['CherenkovHits']
        
        for chid in hits:
            if len(hits[chid]) > maxNumPhotonsInPixel:
                maxNumPhotonsInPixel = len(hits[chid])
        
        if maxNumPhotonsInPixel > level:
            eventDataDict[event_id]['mostSimpleTrigger'] = True
        else:
            eventDataDict[event_id]['mostSimpleTrigger'] = False

    return run


def MakeAndSaveAdcData( run , filename, adcdictname = 'AdcDict', simdictname = 'SimInfo'):
    
    logger.debug("MakeAndSaveAdcData starting up")
    
    PulseIntegral = run['settings']['SipmInfo']['PulseIntegral']    # in mV s
    MeanPulseWeight = run['settings']['SipmInfo']['MeanAmplitude']  # in "number of avalanches"
    Rate = (run['settings']['PhotonNoiseInfo']['NsbRate']+run['settings']['PhotonNoiseInfo']['DcRate']) # in Hz
    
    ac_coupling_effect = PulseIntegral * Rate * MeanPulseWeight     # in mV
    logger.debug("AC coupling offset: %f", ac_coupling_effect)
    
    pulse_function = run['settings']['SipmInfo']['standard_pulse_function']
    
    NoiseLevel = run['settings'][adcdictname]['NoiseLevel']

    AdcFullScale = run['settings'][adcdictname]['AdcFullScale']
    AdcOffset = run['settings'][adcdictname]['AdcOffset']
    AdcResolution = run['settings'][adcdictname]['AdcResolution']
    
    
    SimulatedTime = run['settings'][simdictname]['SimulatedTime']
    SamplingFreqency = run['settings'][simdictname]['SamplingFreqency']
    StartTimeOffset = run['settings'][simdictname]['StartTimeOffset']
    
    NumberOfSamples = int(SimulatedTime*SamplingFreqency)
    StartTimeOffsetSamples = int(StartTimeOffset*SamplingFreqency)
    
    from fitswriter import RawDataFitsWriter2
    fitsfile = RawDataFitsWriter2(filename, run)

    
    eventDataDict = run['eventData']
    for event_id in eventDataDict:
        rawdata = np.zeros( (1440, NumberOfSamples) )
        x = np.linspace(StartTimeOffset,SimulatedTime-StartTimeOffset,NumberOfSamples,False)
        
        hits = eventDataDict[event_id]['AllHitsWithWeights']
        for chid in hits:
            for t,weight in hits[chid]:
                pulse_function(t,rawdata[chid],weight)
                
    
        ##################################### AC coupling ########################
        rawdata -= ac_coupling_effect
        
        ############### Electronics noise ########################################
        rawdata += np.random.normal( loc=0., scale=NoiseLevel, size=rawdata.shape)
    
        ############ ADC simulation, i.e. digitization ###########################        
        adcdata = rawdata.copy()
        adcdata += AdcOffset
        adcdata *= AdcResolution/AdcFullScale
        adcdata = adcdata.astype('int16')
            
        ############### Save event to fitsfile ###################################
            # Format
            #~ ('EventNum', '<u4'), 
            #~ ('TriggerNum', '<u4'), 
            #~ ('TriggerType', '<u4'), 
            #~ ('NumBoards', '<u4'), 
            #~ ('Errors', '|u1', 4), 
            #~ ('SoftTrig', '<u4'), 
            #~ ('UnixTimeUTC', '<u4', 2), 
            #~ ('BoardTime', '<u4', 40), 
            #~ ('StartCellData', '<u2', 1440), 
            #~ ('StartCellTimeMarker', '<u2', 160), 
            #~ ('Data', '<i2', (1440,1024))]) )
        
        fitsfile.rawdata['Data'] = adcdata
        fitsfile.rawdata['EventNum'] = event_id
        fitsfile.rawdata['NumBoards'] = 40
        fitsfile.write()

    logger.debug("MakeAndSaveAdcData ended.")

def SomeThingElse():
    
#~ plt.ion()
#~ import plotters
#~ camplotter = plotters.CamPlotter('cam', dpi=80, s=80)
#~ fig2 = plt.figure()
#~ import matplotlib.pyplot as plt

    
    ph_in_pix = np.zeros(1440)
    pixels = pd['chid'][ids].astype(int)
    for pix in pixels:
        ph_in_pix[pix] += 1
    #print "event=%d  photons=%d" % (event_id, len(ids))
    #print pixels
    #print ("energy=%f GeV" % event_headers[event_id]['total energy in GeV'])
    
    #calculating core distace:
    core = np.array(event_headers[event_id]['core location for scattered events in cm: (x,y)'][0])
    core_dist= np.sqrt((core*core).sum())
    #print "core dist=%f m" % (core_dist/100,)
    
    #~ camplotter(ph_in_pix)
    #~ plt.xlim(-22,22)
    #~ plt.ylim(-22,22)
    #~ 
    #~ plt.figure(fig2.number)
    #~ plt.cla()
    #~ chidset = set(chids.tolist())
    #~ for chid in chidset:
        #~ plt.plot(x,adcdata[chid],'.:',label=str(chid), hold=True)
    #~ plt.legend()
    #~ 
